package com.lucifer.cloud.boot.blog.chat.third;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.lark.oapi.Client;
import com.lark.oapi.core.request.RequestOptions;
import com.lark.oapi.core.response.BaseResponse;
import com.lark.oapi.service.docs.v1.model.GetContentReq;
import com.lark.oapi.service.docs.v1.model.GetContentResp;
import com.lark.oapi.service.docs.v1.model.GetContentRespBody;
import com.lark.oapi.service.docx.v1.model.RawContentDocumentReq;
import com.lark.oapi.service.docx.v1.model.RawContentDocumentResp;
import com.lark.oapi.service.docx.v1.model.RawContentDocumentRespBody;
import com.lark.oapi.service.drive.v1.model.File;
import com.lark.oapi.service.drive.v1.model.ListFileReq;
import com.lark.oapi.service.drive.v1.model.ListFileResp;
import com.lark.oapi.service.drive.v1.model.ListFileRespBody;

import com.lucifer.cloud.boot.blog.chat.config.FeishuConfig;
import com.lucifer.cloud.boot.blog.chat.third.model.FeishuAkRequest;
import com.lucifer.cloud.boot.blog.chat.third.model.FeishuAkResponse;
import com.lucifer.cloud.boot.blog.chat.util.JsonUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

@Service
@RequiredArgsConstructor
public class FeishuService {

    private final Client client;

    private final RestTemplate restTemplate;

    private final FeishuConfig feishuConfig;

    /**
     * <a href="https://open.feishu.cn/document/server-docs/authentication-management/access-token/app_access_token_internal">...</a>
     */
    public static final String AK_URL = "https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal";

    private static final Cache<String, String> ACCESS_TOKEN_CACHE = Caffeine.newBuilder()
        .maximumSize(1000)
        .expireAfterWrite(100, TimeUnit.MINUTES)
        .build();


    private String getAccessToken(String appId) {
        return ACCESS_TOKEN_CACHE.get(appId, s -> {
            HttpEntity<FeishuAkRequest> request = new HttpEntity<>(new FeishuAkRequest(feishuConfig.getAppId(),
                feishuConfig.getAppSecret()));
            var response = Optional.of(restTemplate.exchange(AK_URL, HttpMethod.POST, request, FeishuAkResponse.class));
            if (response.map(HttpEntity::getBody).map(FeishuAkResponse::getCode).filter(code -> code.equals(0)).isEmpty()) {
                throw new RuntimeException("failed to obtain access token, the response body is %s".formatted(response.get()));
            }
            return response.get().getBody().getAppAccessToken();
        });
    }

    public String readDoc(String docId) {
        RawContentDocumentReq req = RawContentDocumentReq.newBuilder()
            .lang(0)
            .documentId(docId)
            .build();
        Optional<RawContentDocumentResp> resp;
        try {
            resp = resolveException(client.docx().v1().document()
                .rawContent(req, RequestOptions.newBuilder()
                    .userAccessToken(getAccessToken(feishuConfig.getAppId()))
                    .build()));
        } catch (Exception e) {
            throw new RuntimeException("failed to read the document", e);
        }
        return resp.map(BaseResponse::getData)
            .map(RawContentDocumentRespBody::getContent)
            .orElse("");
    }


    public String readMarkdown(String docId) {
        GetContentReq req = GetContentReq.newBuilder()
                .docToken(docId)
                .docType("docx")
                .contentType("markdown")
                .lang("zh")
                .build();
        Optional<GetContentResp> resp;
        try {
            GetContentResp getContentResp = client.docs().v1().content().get(req, RequestOptions.newBuilder()
                    .userAccessToken(getAccessToken(feishuConfig.getAppId()))
                    .build());
            resp = resolveException(getContentResp);
        } catch (Exception e) {
            throw new RuntimeException("failed to read the document", e);
        }

        return resp.map(BaseResponse::getData)
                .map(GetContentRespBody::getContent)
                .orElse("");
    }

    public List<File> listDocs() {
        //If the pagination size is exceeded, use hasMore to fetch the next batch, the logic for fetching the next batch is not written here
        ListFileReq req = ListFileReq.newBuilder()
            .pageSize(50)
            .folderToken(feishuConfig.getRootFolder())
            .build();
        Optional<ListFileResp> resp;
        try {
            resp = resolveException(client.drive().v1().file().list(req, RequestOptions.newBuilder()
                .userAccessToken(getAccessToken(feishuConfig.getAppId()))
                .build()));
        } catch (Exception e) {
            throw new RuntimeException("failed to list the docs", e);
        }
        return resp.map(BaseResponse::getData)
            .map(ListFileRespBody::getFiles)
            .map(Arrays::asList)
            .orElse(new ArrayList<>());
    }

    private <T extends BaseResponse<?>> Optional<T> resolveException(T response) {
        if (!response.success()) {
            throw new RuntimeException(
                "failed to call the api function, response body is %s".formatted(JsonUtil.toJsonString(response)));
        }
        return Optional.of(response);
    }

}

